package jp.co.nes.awssupport.common.dao;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.dbutils.QueryRunner;

import jp.co.nes.awssupport.common.base.CommonException;
import jp.co.nes.awssupport.common.bean.BaseBean;
import jp.co.nes.awssupport.common.utility.DbConnUtil;

/**
 * JDBCで更新・読み込みを実施するクラスで使用する基底クラスです。<br>
 * {@link BaseJdbcDao#read(ReadHandler, Object...)}を使用する場合は、{@link ReadHandler}を作成し、
 * {@link BaseJdbcDao#toConditionList(ReadType, Object...)}をサブクラスでオーバーライドします。<br>
 * {@link BaseJdbcDao#insert(BaseBean)}を使用する場合は、{@link BaseJdbcDao#getInsQuery()},{@link BaseJdbcDao#toInsObjectList(BaseBean)}を
 * サブクラスでオーバーライドします。<br>
 * {@link BaseJdbcDao#insert(List)},{@link BaseJdbcDao#insert(List, int)}を使用する場合は、
 * {@link BaseJdbcDao#getBatchInsQuery()},{@link BaseJdbcDao#toBatchInsObjectList(BaseBean)}を
 * サブクラスでオーバーライドします。<br>
 * {@link BaseJdbcDao#save(BaseBean)}を使用する場合は、{@link BaseJdbcDao#getSvQuery()},{@link BaseJdbcDao#toSvObjectList(BaseBean)}を
 * サブクラスでオーバーライドします。<br>
 * {@link BaseJdbcDao#save(List)},{@link BaseJdbcDao#save(List, int)}を使用する場合は、
 * {@link BaseJdbcDao#getBatchSvQuery()},{@link BaseJdbcDao#toBatchSvObjectList(BaseBean)}を
 * サブクラスでオーバーライドします。<br>
 * {@link BaseJdbcDao#update(BaseBean)}を使用する場合は、{@link BaseJdbcDao#getUpdQuery()},{@link BaseJdbcDao#toUpdObjectList(BaseBean)}を
 * サブクラスでオーバーライドします。<br>
 * {@link BaseJdbcDao#update(List)},{@link BaseJdbcDao#update(List, int)}を使用する場合は、
 * {@link BaseJdbcDao#getBatchUpdQuery()},{@link BaseJdbcDao#toBatchUpdObjectList(BaseBean)}を
 * サブクラスでオーバーライドします。<br>
 * {@link BaseJdbcDao#delete(BaseBean)}を使用する場合は、{@link BaseJdbcDao#getDelQuery()},{@link BaseJdbcDao#toDelObjectList(BaseBean)}を
 * サブクラスでオーバーライドします。<br>
 * {@link BaseJdbcDao#delete(List)},{@link BaseJdbcDao#delete(List, int)}を使用する場合は、
 * {@link BaseJdbcDao#getBatchDelQuery()},{@link BaseJdbcDao#toBatchDelObjectList(BaseBean)}を
 * サブクラスでオーバーライドします。
 *
 * @param <T> 処理で使用するBeanクラス
 */
public class BaseJdbcDao<T extends BaseBean> {

    /**
     * バッチ更新に関する区分です。
     */
    private enum BatchExecuteType {
        BATHC_INS, BATHC_SV, BATHC_UPD, BATHC_DEL
    };

    /**
     * デフォルトコンストラクタです。
     */
    public BaseJdbcDao() {
    }

    /**
     * 読み込みハンドラに設定されている内容に従いデータを読み込みます。
     *
     * @param readHandler 読み込みハンドラ
     * @param conditions 検索条件
     * @return 存在有無(true: 有, false: 無)
     */
    public boolean read(ReadHandler<T> readHandler, Object... conditions) {
        try {
            Connection connection = DbConnUtil.getConnForLargeRead();

            Object[] params = toConditionList(readHandler.getReadType(), conditions).toArray();

            String select = readHandler.getReadType().getSelect();

            QueryRunner runner = new QueryRunner();
            return runner.query(connection, select, readHandler, params);
        } catch (SQLException e) {
            throw new CommonException(e);
        }
    }

    /**
     * 引数の内容を登録します。
     *
     * @param bean Beanクラス
     */
    public void insert(T bean) {
        executeQuery(getInsQuery(), toInsObjectList(bean).toArray());
    }

    /**
     * 引数の内容を登録(バッチ更新)します。
     *
     * @param beanList Beanリスト
     */
    public void insert(List<T> beanList) {
        insert(beanList, Integer.MAX_VALUE);
    }

    /**
     * 引数の内容を登録(バッチ更新)します。
     *
     * @param beanList Beanリスト
     * @param max バッチ更新の最大数
     */
    public void insert(List<T> beanList, int max) {
        executeBatchQuery(beanList, max, BatchExecuteType.BATHC_INS);
    }

    /**
     * 引数の内容を登録・修正します。
     *
     * @param bean Beanクラス
     */
    public void save(T bean) {
        executeQuery(getSvQuery(), toSvObjectList(bean).toArray());
    }

    /**
     * 引数の内容を登録・修正(バッチ更新)します。
     *
     * @param beanList Beanリスト
     */
    public void save(List<T> beanList) {
        save(beanList, Integer.MAX_VALUE);
    }

    /**
     * 引数の内容を登録・修正(バッチ更新)します。
     *
     * @param beanList Beanリスト
     * @param max バッチ更新の最大数
     */
    public void save(List<T> beanList, int max) {
        executeBatchQuery(beanList, max, BatchExecuteType.BATHC_SV);
    }

    /**
     * 引数の内容を修正します。
     *
     * @param bean Beanクラス
     */
    public void update(T bean) {
        executeQuery(getUpdQuery(), toUpdObjectList(bean).toArray());
    }

    /**
     * 引数の内容を修正(バッチ更新)します。
     *
     * @param beanList Beanリスト
     */
    public void update(List<T> beanList) {
        update(beanList, Integer.MAX_VALUE);
    }

    /**
     * 引数の内容を修正(バッチ更新)します。
     *
     * @param beanList Beanリスト
     * @param max バッチ更新の最大数
     */
    public void update(List<T> beanList, int max) {
        executeBatchQuery(beanList, max, BatchExecuteType.BATHC_UPD);
    }

    /**
     * 引数の内容を削除します。
     *
     * @param bean Beanクラス
     */
    public void delete(T bean) {
        executeQuery(getDelQuery(), toDelObjectList(bean).toArray());
    }

    /**
     * 引数の内容を削除(バッチ更新)します。
     *
     * @param beanList Beanリスト
     */
    public void delete(List<T> beanList) {
        delete(beanList, Integer.MAX_VALUE);
    }

    /**
     * 引数の内容を削除(バッチ更新)します。
     *
     * @param beanList Beanリスト
     * @param max バッチ更新の最大数
     */
    public void delete(List<T> beanList, int max) {
        executeBatchQuery(beanList, max, BatchExecuteType.BATHC_DEL);
    }

    /**
     * クエリーを実行します。
     *
     * @param query 実行するクエリー
     * @param params クエリーに設定するパラメータ
     */
    protected void executeQuery(String query, Object[] params) {
        try {
            Connection connection = DbConnUtil.getConn();
            QueryRunner runner = new QueryRunner();

            runner.update(connection, query, params);
        } catch (SQLException e) {
            throw new CommonException(e);
        }
    }

    /**
     * クエリーを実行します(バッチ更新)。
     *
     * @param beanList 処理で使用するBeanリスト
     * @param max バッチ更新時の最大数
     * @param batchExecuteType バッチ更新に関する区分
     */
    protected void executeBatchQuery(List<T> beanList, int max, BatchExecuteType batchExecuteType) {
        try {
            Connection connection = DbConnUtil.getConn();

            List<Object[]> paramList = new ArrayList<>();

            String query = getBatchExecuteQuery(batchExecuteType);

            int count = 0;
            QueryRunner runner = new QueryRunner();
            for (T bean : beanList) {
                count++;
                paramList.add(toBatchObjectList(batchExecuteType, bean).toArray());
                if (count >= max) {
                    Object[][] params = paramList.toArray(new Object[0][0]);
                    runner.batch(connection, query, params);
                    paramList = new ArrayList<>();
                    count = 0;
                }
            }
            if (count > 0) {
                Object[][] params = paramList.toArray(new Object[0][0]);
                runner.batch(connection, query, params);
            }
        } catch (SQLException e) {
            throw new CommonException(e);
        }
    }

    /**
     * バッチ更新時に使用するクエリーを返却します。
     *
     * @param batchExecuteType バッチ更新に関する区分
     * @return バッチ更新時に使用するクエリー
     */
    protected String getBatchExecuteQuery(BatchExecuteType batchExecuteType) {
        String result = null;
        switch (batchExecuteType) {
        case BATHC_INS:
            result = getBatchInsQuery();
            break;
        case BATHC_SV:
            result = getBatchSvQuery();
            break;
        case BATHC_UPD:
            result = getBatchUpdQuery();
            break;
        case BATHC_DEL:
            result = getBatchDelQuery();
            break;
        default:
            // 通常発生しない
            throw new CommonException("executeType is wrong");
        }
        return result;
    }

    /**
     * バッチ更新時に設定する置換パラメータの内容を返却します。
     *
     * @param batchExecuteType バッチ更新に関する区分
     * @param bean 処理で使用するBean
     * @return バッチ更新時に設定する置換パラメータの内容
     */
    protected List<Object> toBatchObjectList(BatchExecuteType batchExecuteType, T bean) {
        List<Object> resultList = null;
        switch (batchExecuteType) {
        case BATHC_INS:
            resultList = toBatchInsObjectList(bean);
            break;
        case BATHC_SV:
            resultList = toBatchSvObjectList(bean);
            break;
        case BATHC_UPD:
            resultList = toBatchUpdObjectList(bean);
            break;
        case BATHC_DEL:
            resultList = toBatchDelObjectList(bean);
            break;
        default:
            // 通常発生しない
            throw new CommonException("executeType is wrong");
        }
        return resultList;
    }

    /**
     * {@link BaseJdbcDao#insert(BaseBean)}で使用するクエリーを返却します。
     *
     * @return クエリー
     */
    protected String getInsQuery() {
        throw new UnsupportedOperationException("unsupported insert(T) method");
    }

    /**
     * {@link BaseJdbcDao#save(BaseBean)}で使用するクエリーを返却します。
     *
     * @return クエリー
     */
    protected String getSvQuery() {
        throw new UnsupportedOperationException("unsupported save(T) method");
    }

    /**
     * {@link BaseJdbcDao#update(BaseBean)}で使用するクエリーを返却します。
     *
     * @return クエリー
     */
    protected String getUpdQuery() {
        throw new UnsupportedOperationException("unsupported update(T) method");
    }

    /**
     * {@link BaseJdbcDao#delete(BaseBean)}で使用するクエリーを返却します。
     *
     * @return クエリー
     */
    protected String getDelQuery() {
        throw new UnsupportedOperationException("unsupported delete(T) method");
    }

    /**
     * {@link BaseJdbcDao#insert(List, int)}で使用するクエリーを返却します。
     *
     * @return クエリー
     */
    protected String getBatchInsQuery() {
        throw new UnsupportedOperationException("unsupported insert(List<T>, int) method");
    }

    /**
     * {@link BaseJdbcDao#save(List, int)}で使用するクエリーを返却します。
     *
     * @return クエリー
     */
    protected String getBatchSvQuery() {
        throw new UnsupportedOperationException("unsupported save(List<T>, int) method");
    }

    /**
     * {@link BaseJdbcDao#update(List, int)}で使用するクエリーを返却します。
     *
     * @return クエリー
     */
    protected String getBatchUpdQuery() {
        throw new UnsupportedOperationException("unsupported update(List<T>, int) method");
    }

    /**
     * {@link BaseJdbcDao#delete(List, int)}で使用するクエリーを返却します。
     *
     * @return クエリー
     */
    protected String getBatchDelQuery() {
        throw new UnsupportedOperationException("unsupported delete(List<T>, int) method");
    }

    /**
     * {@link BaseJdbcDao#read(ReadHandler, Object...)}で使用する検索条件の置換パラメータの内容を
     * リストに設定して、返却します。
     *
     * @param readType 日次集計結果を読み込む際の区分
     * @param conditions 検索条件の置換パラメータの内容
     * @return 検索条件の置換パラメータの内容を設定したリスト
     */
    protected List<Object> toConditionList(ReadType readType, Object... conditions) {
        throw new UnsupportedOperationException("unsupported read method");
    }

    /**
     * {@link BaseJdbcDao#insert(BaseBean)}で使用する置換パラメータの内容をリストに設定して、返却します。
     *
     * @param bean 処理で使用するBean
     * @return 置換パラメータの内容を設定したリスト
     */
    protected List<Object> toInsObjectList(T bean) {
        throw new UnsupportedOperationException("unsupported insert(T) method");
    }

    /**
     * {@link BaseJdbcDao#save(BaseBean)}で使用する置換パラメータの内容をリストに設定して、返却します。
     *
     * @param bean 処理で使用するBean
     * @return 置換パラメータの内容を設定したリスト
     */
    protected List<Object> toSvObjectList(T bean) {
        throw new UnsupportedOperationException("unsupported save(T) method");
    }

    /**
     * {@link BaseJdbcDao#update(BaseBean)}で使用する置換パラメータの内容をリストに設定して、返却します。
     *
     * @param bean 処理で使用するBean
     * @return 置換パラメータの内容を設定したリスト
     */
    protected List<Object> toUpdObjectList(T bean) {
        throw new UnsupportedOperationException("unsupported update(T) method");
    }

    /**
     * {@link BaseJdbcDao#delete(BaseBean)}で使用する置換パラメータの内容をリストに設定して、返却します。
     *
     * @param bean 処理で使用するBean
     * @return 置換パラメータの内容を設定したリスト
     */
    protected List<Object> toDelObjectList(T bean) {
        throw new UnsupportedOperationException("unsupported delete(T) method");
    }

    /**
     * {@link BaseJdbcDao#insert(List, int)}で使用する置換パラメータの内容をリストに設定して、返却します。
     *
     * @param bean 処理で使用するBean
     * @return 置換パラメータの内容を設定したリスト
     */
    protected List<Object> toBatchInsObjectList(T bean) {
        throw new UnsupportedOperationException("unsupported insert(List<T>) or insert(List<T>, int) method");
    }

    /**
     * {@link BaseJdbcDao#save(List, int)}で使用する置換パラメータの内容をリストに設定して、返却します。
     *
     * @param bean 処理で使用するBean
     * @return 置換パラメータの内容を設定したリスト
     */
    protected List<Object> toBatchSvObjectList(T bean) {
        throw new UnsupportedOperationException("unsupported save(List<T>) or save(List<T>, int) method");
    }

    /**
     * {@link BaseJdbcDao#update(List, int)}で使用する置換パラメータの内容をリストに設定して、返却します。
     *
     * @param bean 処理で使用するBean
     * @return 置換パラメータの内容を設定したリスト
     */
    protected List<Object> toBatchUpdObjectList(T bean) {
        throw new UnsupportedOperationException("unsupported update(List<T>) or update(List<T>, int) method");
    }

    /**
     * {@link BaseJdbcDao#delete(List, int)}で使用する置換パラメータの内容をリストに設定して、返却します。
     *
     * @param bean 処理で使用するBean
     * @return 置換パラメータの内容を設定したリスト
     */
    protected List<Object> toBatchDelObjectList(T bean) {
        throw new UnsupportedOperationException("unsupported delete(List<T>) or delete(List<T>, int) method");
    }
}
